本文同步更新於blog
高階模組不應該依賴低階模組。它們都應該依賴抽象。
抽象不應該依賴細節。細節應該依賴抽象。
目的是把高階模組對低階模組的依賴解耦,
改為高階模組依賴定義出的抽象介面,
而由低階模組去實現介面。
其中的依賴關係被顛倒,使得低階模組依賴於高階的抽象介面。
Def. 發電:泛指從其它種類的能源轉換為電力的過程。
最初我們有一個電力系統,發電系統採用火力發電,
之後再由輸電系統,配給各個用戶。
今天我們想要抽換發電系統,改為風力發電。
卻發現原本的電力系統強耦合於火力發電,不易抽換。
透過定義出抽象的發電介面,我們可以讓電力系統依賴於發電介面,
並讓火力發電、風力發電去實作發電介面。
因為依賴抽象化,未來就更容易抽換發電系統了。
以下提供範例程式碼:
情境:目前電力系統採用火力發電
<?php
namespace App\SOLID\DIP\PowerSystems;
class ThermalPower
{
public function generatePower()
{
return '電力';
}
}
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\ThermalPower;
class Program
{
public function getPower()
{
$thermalPower = new ThermalPower();
return $thermalPower->generatePower();
}
}
隨著科技發展,現在我們想要改成用風力發電來取代火力發電,
卻發現原本的程式,強耦合在ThermalPower。
(依賴了具體的類別)
我們決定定義一個抽象的介面,
讓程式依賴在介面,並由各個發電方式實作介面。
改變彼此的依賴關係。
需求一:定義抽象介面,改變電力系統與火力發電的依賴關係
<?php
namespace App\SOLID\DIP\PowerSystems\Contracts;
interface PowerGeneratable
{
public function generatePower();
}
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;
class ThermalPower implements PowerGeneratable
{
public function generatePower()
{
return '電力';
}
}
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;
class Program
{
public function getPower(PowerGeneratable $powerGeneration)
{
return $powerGeneration->generatePower();
}
}
(註:現在要用什麼樣的發電方式,會交由客戶端決定)
<?php
namespace Tests\Feature\SOLID\DIP\PowerSystems;
use PHPUnit\Framework\TestCase;
use App\SOLID\DIP\PowerSystems\Program;
use App\SOLID\DIP\PowerSystems\ThermalPower;
class ProgramTest extends TestCase
{
/**
* @var Program
*/
protected $sut;
protected function setUp(): void
{
$this->sut = new Program();
}
public function testGetPowerByThermalPower()
{
$expected = '電力';
$powerGeneration = new ThermalPower();
$actual = $this->sut->getPower($powerGeneration);
$this->assertEquals($expected, $actual);
}
}
需求二:新增風力發電
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;
class WindPower implements PowerGeneratable
{
public function generatePower()
{
return '電力';
}
}
<?php
namespace Tests\Feature\SOLID\DIP\PowerSystems;
use PHPUnit\Framework\TestCase;
use App\SOLID\DIP\PowerSystems\Program;
use App\SOLID\DIP\PowerSystems\ThermalPower;
use App\SOLID\DIP\PowerSystems\WindPower;
class ProgramTest extends TestCase
{
/**
* @var Program
*/
protected $sut;
protected function setUp(): void
{
$this->sut = new Program();
}
public function testGetPowerByThermalPower()
{
$expected = '電力';
$powerGeneration = new ThermalPower();
$actual = $this->sut->getPower($powerGeneration);
$this->assertEquals($expected, $actual);
}
public function testGetPowerByWindPower()
{
$expected = '電力';
$powerGeneration = new WindPower();
$actual = $this->sut->getPower($powerGeneration);
$this->assertEquals($expected, $actual);
}
}
最後附上類別圖比較:
依賴具體的火力發電
依賴抽象的發電介面,並由各個發現方式實作介面
(可以觀察火力發電與風力發電的依賴方向,是不是反轉了呢)
ʕ •ᴥ•ʔ:重讀一次DIP發現重點在於,如何定義出足夠抽象的介面。